/**
 * @class {CosmicScope} 宇宙尺度范围
 * @author DuskyHuang 圣子
 * @description 抽象类、是大级别宇宙范围的父类，用于加载内部子结构。
*/

import { _decorator, Component, Node, Material, MeshRenderer, gfx, Vec3, Prefab } from 'cc';
import { core, utils } from 'cfw';
import { LAYER_MAIN_3D, Res, SceneGraph } from 'const';
import { attenuate, equator2ecliptic, polar2cartesian } from 'algorithms';
import { createNodeAt, setLabelStyle } from 'helpers';
import { cosmos, gizmos, ui, UIProxy } from 'proxies';
import { CelestialLabel } from '../ui/items/celestial-label';
import { BUILD } from 'cc/env';
import { Task } from 'notifications';
const { ccclass, property, disallowMultiple } = _decorator;

@ccclass('cocosmos.ComicScope')
@disallowMultiple(true)
export abstract class CosmicScope extends Component {

    @property(Material) public matLine: Material = null;

    public get substructures(): Node[] { return this._substructures.children; }
    protected abstract createSubParticles(): void;
    protected abstract approachScope1stTime(): void;
    protected abstract _substructure_key: substructures;
    protected abstract _attenuation: attenuation;
    protected PrefabKey: string = null;
    protected offsetY: number | null = null;

    public update(dt?: time.sec): void {
        const { head, tail } = this._attenuation, { pow10 } = cosmos;
        const { showStructs } = gizmos, { showNotes } = ui;
        if (pow10 > head && pow10 < tail) {
            this._subparts.active = true;
            this._substructures.active = showStructs;
            const att = attenuate(pow10, this._attenuation);
            this.matLine.passes[0].setUniform(this._handle, GroupLineStrength * att);
            if (showStructs && showNotes) {
                this._claimLabels();
                for (const label of this._labels) {
                    if (!label.target) continue;
                    if (label === this._labels.head) {
                        label.alpha = att;
                    } else {
                        const lenSqr = Vec3.squaredDistance(label.target.worldPosition, Vec3.ZERO);
                        label.alpha = attenuate(lenSqr, NameLabelAtt, true);
                    }
                }
            } else this._freeLabels();
        } else {
            this._substructures.active = this._subparts.active = false;
            this._freeLabels();
        }
    }

    protected onDisable(): void {
        this._freeLabels();
    }

    protected onLoad(): void {
        this._configs = core.res.getConfigItem<Record<string,
            groupConfigs>>(Res.Configs.SUBSTRUCTURES, this._substructure_key) ?? {};
        this.createSubstructures();
        this.createSubParticles();
        this.init();
        core.on(Task.Load.APPROACH_SCOPE, this._onApproachScope, this);
    }

    private _onApproachScope(name: string): void {
        if (name === this.node.name) this.approachScope1stTime();
    }

    protected init(): void { }

    protected createSubstructures(): void {
        const Capital = this._substructure_key.charAt(0).toUpperCase() + this._substructure_key.substring(1);
        this._substructures = createNodeAt(Capital, LAYER_MAIN_3D, this.node);
        const Substructure = core.res.getPrefab(this.PrefabKey ?? SUBSTRUCTURE);
        for (let key in this._configs) {
            const { pos: [x = 0, y = 0, z = 0], scale: [sx = 1, sy = 1, sz = 1], rotate: [rx = 0, ry = 0, rz = 0] } = this._configs[key];
            if (!BUILD && USE_HMS) {
                this._createByHms(key, Substructure);
                continue;
            }
            const subgroup = createNodeAt(key, LAYER_MAIN_3D, this._substructures);
            subgroup.setScale(sx, sy, sz);
            subgroup.setPosition(x, y, z);
            subgroup.setRotationFromEuler(rx, ry, rz);
            const substructure = createNodeAt('struct-box', LAYER_MAIN_3D, subgroup, Substructure);
            substructure.getComponent(MeshRenderer).setSharedMaterial(this.matLine, 0);
        }
        this._handle = this.matLine.passes[0]?.getHandle('mainColor', 3, gfx.Type.FLOAT);
    }

    /**
     * @deprecated
     * 只用于配置数据时计算，正式加载不再使用天球坐标
    */
    private _createByHms(key: string, Substructure: Prefab): void {
        const { rotate: [rx, ry, rz] } = this._configs[key];
        const { ra_hms, dec_hms, dist } = this._configs[key];
        const [H, M, S] = ra_hms.split(':');
        const RA = ((+H) + (+M) / 60 + (+S) / 60) * 15;
        const [h, m, s] = dec_hms.split(':');
        const Dec = ((+h) + (+m) / 60 + (+s) / 60);
        polar2cartesian(RA, Dec, dist / 100, v3a);
        equator2ecliptic(v3a, v3a);
        const subgroup = createNodeAt(key, LAYER_MAIN_3D, this._substructures);
        subgroup.setScale(1, 1, 1);
        subgroup.setPosition(v3a);
        subgroup.setRotationFromEuler(rx, ry, rz);
        const substructure = createNodeAt('struct-box', LAYER_MAIN_3D, subgroup, Substructure);
        substructure.getComponent(MeshRenderer).setSharedMaterial(this.matLine, 0);
    }

    private _freeLabels(): void {
        if (notEmptyArr(this._labels)) this._labels.forEach(label => label.free());
        this._labels.length = 0;
    }

    private _claimLabels(): void {
        if (notEmptyArr(this._labels)) return;
        const camera = utils.findCamera(COSMOS, DEEP);
        this._substructures.children.forEach(subgroup => {
            const label = CelestialLabel.claim('struct', this._substructure_key);
            label.target = subgroup;
            label.camera = camera;
            setLabelStyle(label, 'struct', this._substructure_key);
            label.setOffset(v3a.set(0, this.offsetY ?? 1.2, 0));
            // label.string = this._configs[subgroup.name].cname;
            const { name, cname } = this._configs[subgroup.name];
            label.config = { en: name, cn: cname };
            this._labels.push(label);
        });
    }

    protected _subparts: Node;
    protected _substructures: Node;
    protected _labels: CelestialLabel[] = [];
    protected _configs: Record<string, groupConfigs>;
    protected _handle: number;
}

const { COSMOS, Cameras: { DEEP } } = SceneGraph;
const { NameLabelAtt, GroupLineStrength } = UIProxy;
const { Prefabs: { SUBSTRUCTURE } } = Res;
const v3a = new Vec3;
const USE_HMS = false;